/** SocketManager */
package postpic.server;

import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.imageio.ImageIO;

import org.json.JSONArray;
import org.json.JSONObject;

import postpic.domain.Photo;
import postpic.domain.User;
import postpic.domain.action.Action;
import postpic.domain.session.Request;
import postpic.domain.session.Response;
import postpic.domain.session.UserSession;
import postpic.domain.session.UserSessionThreadLocal;
import postpic.server.db.ConnectionFactory;
import postpic.server.db.ConnectionThreadLocal;
import postpic.server.photo.PhotoAction;
import postpic.server.user.UserAction;

/**
 * SocketManager manages individual sockets concurrently as they make requests.
 */
public class SocketManager implements Runnable {
	private static final Logger LOGGER = Logger.getLogger(SocketManager.class.getName());

	private Socket socket;
	private Writer out;
	private Reader in;
	private BufferedInputStream inStream;
	private Integer sessionId = null;
	private Map<String, Integer> invalidLogins;
	private boolean loginSuccess = true;
	private User user;
	
	private UserAction userAction;
	private PhotoAction photoAction;

	/**
	 * Creates a SocketManager for the given socket.
	 * @param socket
	 */
	public SocketManager(final Socket socket) {
		this.socket = socket;
		userAction = new UserAction();
		photoAction = new PhotoAction();
	}

	/**
	 * Runs the SocketManager thread.
	 */
	public void run() {

		try {
			//Retrieve the socket's streams
			out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ASCII"));
			inStream = new BufferedInputStream(socket.getInputStream());
			in = new InputStreamReader(inStream);

			//Loop indefinitely while requests are still coming in
			for(;;) {
				//Read the request
				Request request = readRequest();

				//Client connection was closed
				if (request == null) {
					return;
				}

				//Process the Request
				Response response = processRequest(request);

				//Write the Response
				writeResponse(response);
			}
		} catch (final IOException ex) {
			LOGGER.warning(ex.getMessage());
		}
	}

	/**
	 * Processes a given Request based off of the Action of the request and
	 * then returns the appropriate Response.
	 * @param request
	 * @return Response */
	private Response processRequest(final Request request) {
		Response response = null;
		JSONObject responseJson = null;
		JSONObject requestJson = request.getRequest();
		Action action = request.getAction();
		
		Connection sessionConn;
		
		//Make sure that the request is for a login action if there is no session
		if (sessionId == null && !(Action.LOGIN.equals(action) || Action.REGISTER.equals(action))) {
			System.out.println("Not logged in");
			responseJson = new JSONObject();
			responseJson.put("msg", "User is not logged in!");
			response = new Response(false, responseJson);
			return response;
		}
		
		System.out.println("Thread: " + Thread.currentThread().getName());
		response = new Response();
		
		//Perform a different task depending on the Action
		switch (action) {
		case LOGIN:
			sessionConn = ConnectionFactory.generateNewConnection();
			ConnectionThreadLocal.set(sessionConn);
			user = userAction.validateLogin(requestJson.getString("username"), requestJson.getString("password"));
			if (user != null) {
				response.setSuccessful(true);
			    //Add the new session
				sessionId = SessionManager.addSession(user.getUserId());
				UserSessionThreadLocal.set(new UserSession(user.getUserId(), sessionId));
				SessionManager.addConnection(sessionId, sessionConn);
				System.out.println("User logged in: " + user.getNameLast() + ", " + user.getNameLast());
				response.setJSON(user.getJSON());

			} else {
				response.setSuccessful(false);
				responseJson = new JSONObject();
				responseJson.put("msg", "Incorrect login credentials.");
				response.setJSON(responseJson);
			}
			break;
		case REGISTER:
			sessionConn = ConnectionFactory.generateNewConnection();
			ConnectionThreadLocal.set(sessionConn);
			user = userAction.registerUser(requestJson.getString("email"), requestJson.getString("firstName"), 
					requestJson.getString("lastName"), requestJson.getString("password"));

			if (user != null) {
				response.setSuccessful(true);
				response.setJSON(user.getJSON());
			}
			try {
				ConnectionThreadLocal.get().close();
			} catch (SQLException e2) {
				// TODO Auto-generated catch block
				e2.printStackTrace();
			}
			ConnectionThreadLocal.set(null);
			break;
		case UPLOAD_PIC:
			InputStream i = null;
			boolean s = true;
			try {
				i = socket.getInputStream();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				s = false;
				e1.printStackTrace();
			}
			try {
				BufferedImage img = ImageIO.read(i);
				new File(user.getUserId().toString()).mkdir();
				Date now = new Date();
				String path = user.getUserId() + "/" + now.getTime() + ".png";
				File imgFile = new File(path);
				System.out.println("Receiving new image.");
				ImageIO.write(img, "png", imgFile);
				System.out.println("Image handled.");
				
				Photo photo = new Photo();
				photo.setCaption(request.getRequest().getString("caption"));
				photo.setUserId(request.getRequest().getInt("userId"));
				photo.setDateAdded(new Date());
				photo.setPhotoUrl(path);
				photoAction.addPhoto(photo);
				System.out.println("Photo added.");
			} catch (IOException e) {
				s = false;
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			response = new Response(s, null);
			break;
		case GET_RECENT_PHOTOS:
			List<Photo> photos = photoAction.getRecentPhotos(user.getUserId());
			responseJson = new JSONObject();
			JSONArray imgArray = new JSONArray();
			
			for (Photo p : photos) {
				imgArray.put(p.getJSON());
			}
			
			responseJson.put("photos", imgArray);
			responseJson.put("count", photos.size());
			response = new Response(true, responseJson);
			try {
				writeResponse(response);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			OutputStream o = null;
			try {
				o = socket.getOutputStream();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			
			for (Photo p : photos) {
				System.out.println("Transmitting photo");
				File f = new File(p.getPhotoUrl());
				BufferedImage img = null;
				try {
					img = ImageIO.read(f);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				if (img != null) {
					try {
						ImageIO.write(img, "PNG", o);
						o.flush();
						System.out.println("Image sent to client.");
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
			break;
		case GET_USER:
			Integer userId = requestJson.getInt("userId");
			user = userAction.getUser(userId);
			
			if (user != null) {
				response.setSuccessful(true);
				response.setJSON(user.getJSON());
			}
			break;
		case ADD_FRIEND:
			String email = requestJson.getString("email");
			response.setSuccessful(userAction.addFriend(email, user));
			break;
		case ADD_COMMENT:
			Integer photoId = requestJson.getInt("photoId");
			break;
		default:
			break;
		}
		
		if (response != null && !response.isSuccessful() && response.getJSON() == null) {
			response.setSuccessful(false);
		}
		
		System.out.println("Finished processing request");
		return response;
	}
	
	private void readImage() {
		File img = new File("test");
		try {
			BufferedImage bImg = ImageIO.read(inStream);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Read an incoming request from the socket's input stream
	 * and return the populated Request object once reading from 
	 * the stream has been completed.
	 * @return Request 
	 * @throws IOException */
	private Request readRequest() throws IOException {
		//Read the request
		String requestStr = "";
		for(;;) {
			int c = in.read();
			if (c == '\r' || c == '\n') break;
			if (c == -1) {
				close();
				return null;
			}
			requestStr += (char) c;
		}

		//Create a new Request with what was read
		Request request = new Request(requestStr);
		LOGGER.info("Got Request: " + request.getJSON());

		return request;
	}

	/**
	 * Writes a given Response to the socket's ouput stream.
	 * @param response
	
	 * @throws IOException */
	private void writeResponse(final Response response) throws IOException {
		out.write(response.toString() + "\r");
		out.flush();
	}

	/**
	 * Performs the actions necessary to close the socket.
	 */
	private void close() {
		if (socket != null) {
			try {
				//Remove the open SESSION
				if (sessionId != null) {
					SessionManager.removeSession(sessionId);
				}

				//Close the socket
				socket.close();
				LOGGER.info("Socket closed");
			} catch (final IOException e) {
				LOGGER.warning("Could not close socket - Session ID: " + sessionId);
			}
		}
	}
	
}
